/* C.Time: Time processing for Arm 5-byte time format */

#include <stdio.h>
#include <limits.h>
#include "kernel.h"
#include "swis.h"

#include "sys/time.h"

/* The "zero" time (ie. 00:00:00 on 01/01/1900) */
TIME TIMENull = { 0, 0, 0, 0, 0 };

TIME TIMEadd (TIME t, int n)
{
	unsigned int sum;
	TIME res;

	sum = t.t[0] + n;
	res.t[0] = sum & 0xFF;

	sum >>= 8;
	sum += t.t[1];
	res.t[1] = sum & 0xFF;

	sum >>= 8;
	sum += t.t[2];
	res.t[2] = sum & 0xFF;

	sum >>= 8;
	sum += t.t[3];
	res.t[3] = sum & 0xFF;

	sum >>= 8;
	sum += t.t[4];
	res.t[4] = sum & 0xFF;

	return res;
}

int TIMEsub (TIME t1, TIME t2)
{
	int n1;
	int n2;

	if (t1.t[4] != t2.t[4])
		return ((t1.t[4] < t2.t[4]) ? INT_MIN : INT_MAX);

	n1 = (t1.t[0]) | (t1.t[1] << 8) | (t1.t[2] << 16) | (t1.t[3] << 24);
	n2 = (t2.t[0]) | (t2.t[1] << 8) | (t2.t[2] << 16) | (t2.t[3] << 24);

	return (n1 - n2);
}

int TIMEcmp (TIME t1, TIME t2)
{
	if (t1.t[4] != t2.t[4])
		return ((t1.t[4] < t2.t[4]) ? -1 : 1);

	if (t1.t[3] != t2.t[3])
		return ((t1.t[3] < t2.t[3]) ? -1 : 1);

	if (t1.t[2] != t2.t[2])
		return ((t1.t[2] < t2.t[2]) ? -1 : 1);

	if (t1.t[1] != t2.t[1])
		return ((t1.t[1] < t2.t[1]) ? -1 : 1);

	if (t1.t[0] != t2.t[0])
		return ((t1.t[0] < t2.t[0]) ? -1 : 1);

	return 0;
}

char *TIMEfmt (const char *fmt, char *buffer, int len, TIME t)
{
	_kernel_swi_regs regs;

	regs.r[0] = (int)(&t);
	regs.r[1] = (int)buffer;
	regs.r[2] = len;
	regs.r[3] = (int)fmt;

	if (_kernel_swi(OS_ConvertDateAndTime, &regs, &regs))
		return 0;

	return buffer;
}

#define CTIME_LEN 26
#define CTIME_FMT "%w3 %m3 %dy %24:%mi:%se %ce%yr\n"

char *TIMEstd (TIME t)
{
	static char buffer[CTIME_LEN];
	_kernel_swi_regs regs;

	regs.r[0] = (int)(&t);
	regs.r[1] = (int)buffer;
	regs.r[2] = CTIME_LEN;
	regs.r[3] = (int)CTIME_FMT;

	if (_kernel_swi(OS_ConvertDateAndTime, &regs, &regs))
		return 0;

	/* Required format has day no as BLANK padded (eg. " 7", not "07") */
	if (buffer[6] == '0')
		buffer[6] = ' ';

	return buffer;
}

TIME TIMEnow(TIME *tp)
{
	int i;
	int blk[2];
	static TIME t;
	char *p = (char *)blk;

	if ( tp == 0 )
		tp = &t;

	p[0] = 3;

	if (_kernel_osword(0x0E, blk) == _kernel_ERROR)
		return TIMENull;

	for ( i = 0; i < 5; ++i )
		tp->t[i] = p[i];

	return *tp;
}

/* Internal routine to accumulate values into a TIME variable */
static void t_accum (TIME *t, int acc, int mul)
{
        int a[5];
        unsigned n = acc;
        int i, j;

        for ( i = 0; i <= 4; ++i )
                a[i] = ( n >> ( i * 8 ) & 0xFF );
        for ( i = 0; i <= 4; ++i )
        {
                n = mul * t->t[i] + a[i];

                t->t[i] = (n & 0xFF);

                for ( j = i + 1; j <= 4; ++j )
                        a[j] += ( n >> ( (j-i) * 8 ) & 0xFF );
        }
}

TIME TIMEmktime (struct tm *timeptr)
{
        int is_ly;
        int ys;
        int days;
        TIME t;

        /* Correct invalid elements of *timeptr */
        mktime(timeptr);

        if ( timeptr->tm_year % 4 != 0 )
                is_ly = 0;
        else if ( timeptr->tm_year % 100 != 0 )
                is_ly = 1;
        else if ( timeptr->tm_year % 400 == 0 )
                is_ly = 1;
        else
                is_ly = 0;


        ys = timeptr->tm_year;

        if ( timeptr->tm_year == 0 )
                days = 0;
        else
        {
                days = 365 * timeptr->tm_year;

                /* +1 per leap year */
                days += ( timeptr->tm_year - 1 ) / 4;

                /* -1 for whole centuries */
                days -= ( timeptr->tm_year - 1 ) / 100;

                /* +1 per leap century */
                days += ( timeptr->tm_year + 299 ) / 400;
        }

        days += timeptr->tm_yday;

        t = TIMENull;
        t_accum(&t,days,1);              /* Set up days */
        t_accum(&t,timeptr->tm_hour,24); /* convert to hours */
        t_accum(&t,timeptr->tm_min,60);  /* convert to mins */
        t_accum(&t,timeptr->tm_sec,60);  /* convert to sec */
        t_accum(&t,0,100);               /* convert to csec */

        return t;
}

#define PARTS_LEN	19
#define PARTS_FMT	"%se%mi%24%dy%mn%ce%yr%wn%dn"
#define GetNum2(p)	(((p)[0] - '0') * 10 + ((p)[1] - '0'))
#define GetNum3(p)	(GetNum2(p) * 10 + ((p)[2] - '0'))
#define GetNum4(p)	(GetNum3(p) * 10 + ((p)[3] - '0'))

struct tm *TIMEstruct (TIME t)
{
	char buffer[PARTS_LEN];
	_kernel_swi_regs regs;
	static struct tm time;

	time.tm_isdst = 0;

	regs.r[0] = (int)(&t);
	regs.r[1] = (int)buffer;
	regs.r[2] = PARTS_LEN;
	regs.r[3] = (int)PARTS_FMT;

	if (_kernel_swi(OS_ConvertDateAndTime, &regs, &regs))
	{
		time.tm_sec  = 0;
		time.tm_min  = 0;
		time.tm_hour = 0;
		time.tm_mday = 0;
		time.tm_mon  = 0;
		time.tm_year = 0;
		time.tm_wday = 0;
		time.tm_yday = 0;
		return &time;
	}

	time.tm_sec  = GetNum2(&buffer[0]);
	time.tm_min  = GetNum2(&buffer[2]);
	time.tm_hour = GetNum2(&buffer[4]);
	time.tm_mday = GetNum2(&buffer[6]);
	time.tm_mon  = GetNum2(&buffer[8])  - 1;	/* Jan = 0 */
	time.tm_year = GetNum4(&buffer[10]) - 1900;	/* Years from 1900 */
	time.tm_wday = (buffer[14] - '0')   - 1;	/* Sun = 0 */
	time.tm_yday = GetNum3(&buffer[15]);
	return &time;
}
